JSON output format

Example 1 - Serialize the Python output format to JSON

import functools
import json

def json_output(decorated):
    """ Run the decorated function,
        serialize the result of that function to JSON,
        and return the JSON string
    """
    # Preserving the help of the inner function
    @functools.wraps(decorated)
    #
    def inner(*args, **kwargs):
        result = decorated(*args, **kwargs)
        return json.dumps(result)
    return inner

def func_01(dict):
    return dict

@json_output
def func_02(dict):
    return dict

d = {'a': 1, 'c': 3, 'b': 2}
res = func_01(d)
print(type(res), res, res['c'])
# <class 'dict'> {'a': 1, 'c': 3, 'b': 2} 3

res = func_02(d)
print(type(res), res, res[2])
# <class 'str'> {"a": 1, "c": 3, "b": 2} a

Example 2

class JSONOutputError(Exception):
    def __init__(self, message):
        self._message = message

    def __str__(self):
        return self._message

def json_err_output(decorated):
    """Run the decorated function,
       serialize the result of that function
       to JSON, and return the JSON string.
    """
    @functools.wraps(decorated)
    def inner(*args, **kwargs):
        try:
            result = decorated(*args, **kwargs)
        except JSONOutputError as ex:
            result = {
                'status': 'error',
                'message': str(ex),
            }
        return json.dumps(result)
    return inner

def func_01():
    raise ValueError('Value error message')

# res = func_01()
# print(res)
# Traceback (most recent call last):
#   File "<>/deco_output_format.py", line 71, in <module>
#     res = error()
#   File "<>/deco_output_format.py", line 69, in error
#     raise ValueError('Value error message')
# ValueError: Value error message

# Function decorated with @json_err_output raises a JSONOutputError
# and you will get a special error handling

@json_err_output
def func_02():
    raise JSONOutputError('This function is erratic.')

res = func_02()
print(res)
# {"status": "error", "message": "This function is erratic."}

Example 3 - decorator with arguments

# The difference between an argument passed to the decorator and
# an argument passed to the function at call time is precisely that.
# An argument to the decorator is processed once, when the function is declared and decorated.
# By contrast, arguments to the function are processed when that function is called.
# See: "Professional Python, Luke Sneeringer, Wrox, 2016, p 16.

class JSONOutputError(Exception):
    def __init__(self, message):
        self._message = message
    def __str__(self):
        return self._message

def json_err_output(indent=None, sort_keys=False):
    """ Run the decorated function,
        serialize the result of that function to JSON,
        and return the JSON string
    """
    def actual_decorator(decorated):                # <---
        @functools.wraps(decorated)
        def inner(*args, **kwargs):
            try:
                result = decorated(*args, **kwargs)
            except JSONOutputError as ex:
                result = {
                    'status': 'error',
                    'message': str(ex),
                }
            return json.dumps(result, indent=indent, sort_keys=sort_keys)
        return inner
    return actual_decorator                         # <---

@json_err_output(indent=4)
def func_02():
    raise JSONOutputError('This function is erratic.')

# return JSON block back with indentation and newlines added
res = func_02()
print(res)
# {
#     "status": "error",
#     "message": "This function is erratic."
# }